package hfsm

import (
	"context"
	"database/sql"
	"errors"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/looplab/fsm"
	"hotgo/internal/consts"
	"hotgo/internal/dao"
	"hotgo/internal/library/dict"
	"hotgo/internal/model/entity"
	"hotgo/internal/service"
	"sync"
)

var DFSM *DistributionFsm

// 分销状态机
type DistributionFsm struct {
	ctx    context.Context
	mapper sync.Map
}

func NewDistributionFsm(ctx context.Context) {
	var handleList []entity.Distribution
	if err := dao.Distribution.Ctx(ctx).
		WhereNotIn(dao.Distribution.Columns().Status, []int{consts.DistributionStatusComplete, consts.DistributionStatusFail}).
		Scan(&handleList); err != nil && !errors.Is(err, sql.ErrNoRows) {
		g.Log().Error(ctx, "获取分销状态机数据失败 err: %v", err)
		return
	}
	DFSM = new(DistributionFsm)
	DFSM = &DistributionFsm{
		ctx: ctx,
	}

	for _, distribution := range handleList {
		DFSM.NewFSM(dict.GetOptionLabel(consts.DistributionStatusOptions, distribution.Status), distribution.TraceId)
	}

	return
}

func (this *DistributionFsm) NewFSM(initialState string, args ...any) {
	traceID := args[0].(string)
	f := fsm.NewFSM(
		initialState,
		fsm.Events{
			{
				Name: consts.DistributionEventProcessing,
				Src:  []string{dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusCreate)},
				Dst:  dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusProcessing),
			},
			{
				Name: consts.DistributionEventComplete,
				Src:  []string{dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusProcessing)},
				Dst:  dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusComplete),
			},
			{
				Name: consts.DistributionEventFail,
				Src:  []string{dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusProcessing)},
				Dst:  dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusFail),
			},
		},
		fsm.Callbacks{
			"enter_" + dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusCreate):     func(ctx context.Context, event *fsm.Event) { this.enterCreate(ctx, event, traceID) },
			"enter_" + dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusProcessing): func(ctx context.Context, event *fsm.Event) { this.enterProcessing(ctx, event, traceID) },
			"enter_" + dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusComplete):   func(ctx context.Context, event *fsm.Event) { this.enterComplete(ctx, event, traceID) },
			"enter_" + dict.GetOptionLabel(consts.DistributionStatusOptions, consts.DistributionStatusFail):       func(ctx context.Context, event *fsm.Event) { this.enterFail(ctx, event, traceID) },
		},
	)
	this.mapper.Store(traceID, f)
}

func (this *DistributionFsm) PushState(ctx context.Context, traceID, event string) error {
	f, err := this.GetFsm(traceID)
	if err != nil {
		return err
	}
	if err = f.Event(ctx, event); err != nil {
		g.Log().Errorf(ctx, "分销状态机事件处理失败 err: %v", err)
		return err
	}
	return nil
}

func (this *DistributionFsm) GetFsm(traceID string) (*fsm.FSM, error) {
	if f, ok := this.mapper.Load(traceID); ok {
		return f.(*fsm.FSM), nil
	}
	return nil, errors.New("分销状态机不存在")
}

func (this *DistributionFsm) enterCreate(ctx context.Context, event *fsm.Event, traceID string) {
	this.PushState(ctx, traceID, consts.DistributionEventProcessing)
}

func (this *DistributionFsm) enterProcessing(ctx context.Context, event *fsm.Event, traceID string) {
	if _, err := dao.Distribution.Ctx(ctx).Where(dao.Distribution.Columns().TraceId, traceID).Data(g.Map{
		dao.Distribution.Columns().Status: consts.DistributionStatusProcessing,
	}).Update(); err != nil {
		g.Log().Errorf(ctx, "分销状态机更新失败 err: %v", err)
		return
	}

	var data entity.Distribution
	if err := dao.Distribution.Ctx(ctx).Where(dao.Distribution.Columns().TraceId, traceID).Scan(&data); err != nil {
		g.Log().Errorf(ctx, "分销状态机获取数据失败 err: %v", err)
		return
	}

	if data.Type == consts.DistributionTypeProduct {
		if err := service.Distribution().ProductDo(ctx, traceID); err != nil {
			g.Log().Errorf(ctx, "分销状态机处理失败 err: %v", err)
			this.PushState(ctx, traceID, consts.DistributionEventFail)
			return
		}
	}
	if data.Type == consts.DistributionTypeVip {
		if err := service.Distribution().PromoProDo(ctx, traceID); err != nil {
			g.Log().Errorf(ctx, "分销状态机处理失败 err: %v", err)
			this.PushState(ctx, traceID, consts.DistributionEventFail)
			return
		}
	}

	this.PushState(ctx, traceID, consts.DistributionEventComplete)
}

func (this *DistributionFsm) enterComplete(ctx context.Context, event *fsm.Event, traceID string) {
	if _, err := dao.Distribution.Ctx(ctx).Where(dao.Distribution.Columns().TraceId, traceID).Data(g.Map{
		dao.Distribution.Columns().Status: consts.DistributionStatusComplete,
	}).Update(); err != nil {
		g.Log().Errorf(ctx, "分销状态机更新失败 err: %v", err)
		return
	}
}

func (this *DistributionFsm) enterFail(ctx context.Context, event *fsm.Event, traceID string) {
	if _, err := dao.Distribution.Ctx(ctx).Where(dao.Distribution.Columns().TraceId, traceID).Data(g.Map{
		dao.Distribution.Columns().Status: consts.DistributionStatusFail,
	}).Update(); err != nil {
		g.Log().Errorf(ctx, "分销状态机更新失败 err: %v", err)
		return
	}
}
